﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Erau.ERBParser
{
	/**
	 * 토큰의 종류를 생각해보자.
	 * 1. 변수명 : 변수명 자체를 만들 때 수식을 쓰는 경우도 있음 Ex) MAXBASE:U:(GET_BASESTATUS(COUNT+1))
	 *    변수명 토큰은 내부적으로 안에 토큰목록을 저장하고 있어야 할 듯.
	 *    변수명의 파싱 결과는 문자열이랑 다르기 때문에 별도의 클래스가 필요할 듯.
	 *    
	 * 2. 문자열: 식내호출 때문에 안에 수식이나 함수호출이 들어갈 수 있음. 
	 *  Ex) 'PRINTFORM %CSVCALLNAME(CFLAG:U:초기링크악마, 0)%\@CFLAG:U:악마변신 ?변신중#변신가능\@'
	 *      'CALLFORM SKILL_NAME_{ABL:U:(LOCALS:2)}' 'PRINTFORM [{LOCAL:3+1}]%RESULTS,20,LEFT%'
	 * 3. 함수명 : 문자열 토큰을 씀.
	 * 
	 */
	public abstract class InstructionToken
	{
		//public EraVar ParseResult;

		/// <summary>
		/// 명령어 토큰 클래스는 파싱 중 발생하는 예외를 직접 처리할 의무를 가지지 않는다.
		/// 즉, 토큰이 던진 파싱 예외는 상위 단계에서 처리한다.
		/// </summary>
		/// <param name="str"></param>
		public abstract void Parse(StringBuffer str);
		public abstract EraVar Eval();
	}

	/// <summary>
	/// 변수 토큰이라고 하고 있지만 정확히는 수식 토큰이 되어야 할 듯.
	/// 얘가 파서의 핵심이자 가장 골때리는 부분이 될 것 같다 -_-;
	/// </summary>
	public class VariableToken : InstructionToken
	{
		public VariableToken(StringBuffer buf) { Parse(buf); }

		override public void Parse(StringBuffer str)
		{

		}

		override public EraVar Eval()
		{
			return new EraString("");
		}
	}

	/// <summary>
	/// 문자열 토큰. 내부에 식중호출이 들어갈 수 있기 때문에 단순 문자열 취급할 수 없음.
	/// </summary>
	public class StringToken : InstructionToken
	{
		// 변수명 토큰은 안에 파싱 가능한 서브 토큰들을 더 가지고 있을 수 있다.
		private List<InstructionToken> subTokens = new List<InstructionToken>();

		//const char[] separator = new char[] { '(', '{', '%' };
		//enum StatusType { RawString, FormattedString, Expression, Call};

		public StringToken(StringBuffer buf) { Parse(buf); }

		override public void Parse(StringBuffer buf)
		{
			string tmp;

			while (!buf.EOS)
			{
				switch (buf.Current)
				{
					// 내부 수식일 때
					case '(': 
						buf.ShiftNext(); // 다음 글자로 이동.

						tmp = buf.GetToken(')');
						if (buf.EOS)
							throw new Exception("문자열 내부 수식 기호('(')가 시작하고 끝나지 않았습니다.");

						// 수식 토큰 추가.
						subTokens.Add(new ExpressionToken(tmp));

						buf.ShiftNext(); // 위의 구문을 통과했다면 현재 위치의 글자는 ')'임.
						break;
					// 형식화 문자열(formatted string)일 때
					case '{':
						buf.ShiftNext(); // 다음 글자로 이동.

						tmp = buf.GetToken('}');
						if (buf.EOS)
							throw new Exception("형식화된 문자열 구문 기호('{')가 시작하고 끝나지 않았습니다.");

						// 형식화 문자열 토큰 추가.
						subTokens.Add(new FormattedStringToken(tmp));

						buf.ShiftNext(); // 위의 구문을 통과했다면 현재 위치의 글자는 '}'임.
						break;
					// 식중 함수호출일 때
					case '%':
						buf.ShiftNext(); // 다음 글자로 이동.

						tmp = buf.GetToken('%');
						if (buf.EOS)
							throw new Exception("식중호출 구문 기호('%')가 시작하고 끝나지 않았습니다.");

						// 식중호출 토큰 추가.
						subTokens.Add(new CallFunctionToken(tmp));

						buf.ShiftNext(); // 위의 구문을 통과했다면 현재 위치는 '%'이 끝나는 부분임.
						break;
					// 그 외의 경우는 일반 문자열임.
					default :
						tmp = buf.GetToken(new char[] { '{', '(', '%' });
						if(tmp != string.Empty)  // 실제로 이렇게 될 가능성이 구조적으로 거의 없으므로 걍 한줄로 줄일 수 있을 듯. NEED_REVIEW
							subTokens.Add(new RawStringToken(tmp));
						break;
				}
			}
		}

		override public EraVar Eval()
		{
			EraString ret = new EraString();

			foreach (InstructionToken t in subTokens)
				ret += t.Eval().ToString();

			return ret;
		}
	}

	/// <summary>
	/// 식중호출용 토큰
	/// </summary>
	public class CallFunctionToken : InstructionToken
	{
		string value;

		public CallFunctionToken(StringBuffer buf) { Parse(buf); }

		/// 따로 파싱 없이 문자열 대입.
		override public void Parse(StringBuffer str) { value = str.Buffer; }

		/// 내부에 보관하고 있는 스트링만 돌려줌.
		public override EraVar Eval() { return new EraString(value); }
	}

	/// <summary>
	/// 
	/// </summary>
	public class FormattedStringToken : InstructionToken
	{
		string value;

		public FormattedStringToken(StringBuffer buf) { Parse(buf); }

		/// 따로 파싱 없이 문자열 대입.
		override public void Parse(StringBuffer str) { value = str.Buffer; }

		/// 내부에 보관하고 있는 스트링만 돌려줌.
		public override EraVar Eval() { return new EraString(value); }
	}

	/// <summary>
	/// 
	/// </summary>
	public class ExpressionToken : InstructionToken
	{
		string value;

		public ExpressionToken(StringBuffer buf) { Parse(buf); }

		/// 따로 파싱 없이 문자열 대입.
		override public void Parse(StringBuffer str) { value = str.Buffer; }

		/// 내부에 보관하고 있는 스트링만 돌려줌.
		public override EraVar Eval() { return new EraString(value); }
	}

	/// <summary>
	/// 내부에 다른 수식이 들어가지 않는, 순수 문자열로만 구성된 토큰. 
	/// </summary>
	public class RawStringToken : InstructionToken
	{
		string value;

		public RawStringToken(StringBuffer buf) { Parse(buf); }

		/// 따로 파싱 없이 문자열 대입.
		override public void Parse(StringBuffer str) { value = str.Buffer; } 

		/// 내부에 보관하고 있는 스트링만 돌려줌.
		public override EraVar Eval() {	return new EraString(value); }
	}
}
